/*
 * MapTrack.java
 *
 * Created on 31 mai 2007, 21:52
 *
 */
package courselog.maputils;

import courselog.CourseLogAppForm;
import courselog.gpsutils.GeoDirection;
import courselog.gpsutils.GeoPoint;
import courselog.gpsutils.TrackPoint;
import courselog.gpsutils.TrackRecord;
import ewe.fx.Brush;
import ewe.fx.Color;
import ewe.fx.Graphics;
import ewe.fx.Pen;
import ewe.fx.Point;
import ewe.fx.Rect;
import ewe.io.File;

/**
 * A track which is able to display on a map.
 *
 * @author CourseLog
 */
public class MapTrack {

    protected final Pen PENHANDLE = new Pen(new Color(0, 0, 0), Pen.SOLID, 2);
    protected final int DISTANCESYMBOLSSQUARE = 32 * 32;
    protected Pen thePenVisited;
    protected Pen thePenNonVisited;
    protected int tPX[] = new int[3];
    protected int tPY[] = new int[3];
    protected TrackRecord myTrack;
    protected int myIndexSelected = -1;
    protected int myIndexNext = 0;
    protected double myTotalLength = 0.0d;
    protected Point ptTmp = null;
    protected GeoDirection gdTmp = null;

    // CONSTRUCTORS
    /** Creates a new instance of MapTrack */
    public MapTrack(Pen pVisited, Pen pNotVisited) {
        this(pVisited, pNotVisited, new TrackRecord());
    }

    /** Creates a new instance of MapTrack with a track*/
    public MapTrack(Pen pVisited, Pen pNotVisited, TrackRecord tr) {
        myTrack = tr;
        int nbP = myTrack.vPoints.size();
        if (nbP > 0) {
            GeoPoint pEnd = (GeoPoint) myTrack.vPoints.elementAt(nbP - 1);
            if (pEnd instanceof TrackPoint) {
                myTotalLength = ((TrackPoint) pEnd).lengthHere;
            } else {
                updateDistancesFromIndex(0);
            }
        }
        thePenVisited = pVisited;
        thePenNonVisited = pNotVisited;
    }

    // ACCESSORS
    /**
     * Number of elements in the track.
     */
    public int getSize() {
        return myTrack.vPoints.size();
    }

    /**
     * Element from the track.
     * @param idx index of the point from the track.
     * @return Element casted to a TrackPoint or null if index is invalid.
     */
    public TrackPoint getPointAt(int idx) {
        if ((idx >= myTrack.vPoints.size()) || (idx < 0)) {
            return null;
        }
        return (TrackPoint) myTrack.vPoints.elementAt(idx);
    }

    /**
     * Total length of the track.
     */
    public double getTotalLength() {
        return myTotalLength;
    }

    /**
     * Getter for next point to visit.
     */
    public TrackPoint getNext() {
        if ((myIndexNext < 0) || (myTrack.vPoints.size() == 0)) {
            return null;
        }
        return (TrackPoint) myTrack.vPoints.elementAt(myIndexNext);
    }

    /**
     * Getter for selected point.
     */
    public TrackPoint getSelected() {
        if (myIndexSelected < 0) {
            return null;
        }
        return (TrackPoint) myTrack.vPoints.elementAt(myIndexSelected);
    }

    /**
     * Setter for selected point.
     */
    public void setSelectedIndex(int sel) {
        if (sel >= myTrack.vPoints.size()) {
            return;
        }
        if (sel < 0) {
            myIndexSelected = -1;
        } else {
            myIndexSelected = sel;
        }
    }

    /**
     * Circular selection of points.
     * @param direction if positive, go to next, if negative previous.
     */
    public void selectNext(int direction) {
        if (myTrack.vPoints.size() <= 0) {
            return;
        }

        if (direction >= 0) {
            myIndexSelected++;
        } else {
            myIndexSelected--;
        }
        if (myIndexSelected >= myTrack.vPoints.size()) {
            myIndexSelected = 0;
        }
        if (myIndexSelected < 0) {
            myIndexSelected = myTrack.vPoints.size() - 1;
        }
    }

    // CLASS METHODS
    /**
     * Add a point to the track
     */
    public void addLast(Point p, GeoGraphable gG) {
        TrackPoint tPt = new TrackPoint();
        tPt = (TrackPoint) gG.getPointFromMap(p.x, p.y, tPt);
        myTrack.vPoints.add(tPt);
//        CourseLogAppForm.dbgL.addToLog("MapTrack.addToLog: tPt="+tPt);
        myIndexSelected = myTrack.vPoints.size() - 1;
        myIndexNext = 0;
        updateDistancesFromIndex(myIndexSelected);
//        CourseLogAppForm.dbgL.addToLog("MapTrack.addToLog: len="+myTotalLength);
    }

    /**
     * Update track points distances starting at a given point.
     * @param idxStart Index of first point to update. If 0, all track is updated.
     */
    public void updateDistancesFromIndex(int idxStart) {
        int last = myTrack.vPoints.size() - 1;
        if ((idxStart < 0) || (idxStart > last)) {
            return;
        } // Bad argument
        double dP = 0.0d;
        TrackPoint tPrec = null;
        if (idxStart > 0) {
            tPrec = ((TrackPoint) myTrack.vPoints.elementAt(idxStart - 1));
            dP = tPrec.lengthHere;
        } else {
            tPrec = ((TrackPoint) myTrack.vPoints.elementAt(idxStart));
            tPrec.lengthHere = dP;
        }
        myTotalLength = dP;
        if (last == 0) {
            return;
        } // Only one point, no distance!
        for (int iP = idxStart; iP <= last; iP++) {
            TrackPoint tP = (TrackPoint) myTrack.vPoints.elementAt(iP);
            dP += tPrec.distance(tP); // Distance from previous point
            tP.lengthHere = dP;
            tPrec = tP;
        }
        myTotalLength = dP;
    }

    /**
     * Remove a point at an index from the track.
     */
    public void removeAt(int idx) {
        myTrack.vPoints.removeElementAt(idx);
        int last = myTrack.vPoints.size() - 1;
        if (last == 0) {
            myIndexSelected = -1;
            return;
        }
        if (idx <= last) {
            updateDistancesFromIndex(idx);
            myIndexSelected = idx;
        } else {
            myIndexSelected = last;
        }
        myIndexNext = 0;
        updateDistancesFromIndex(myIndexNext);
    }

    /**
     * Empty the track.
     */
    public void removeAll() {
        myTrack.vPoints.removeAllElements();
        myIndexSelected = -1;
        myIndexNext = -1;
        myTotalLength = 0.0d;
    }

    /**
     * Find index of closest point from track.
     */
    public int indexOfClosestPoint(Point p, int dMinInt, GeoGraphable gG) {
        double dMin = dMinInt * dMinInt; // comparison is on square distance
        double dT;
        int idxMin = -1;

        if (myTrack == null) {
            return idxMin;
        }

        for (int idx = myTrack.vPoints.size() - 1; idx >= 0; idx--) {
            GeoPoint gT = (GeoPoint) myTrack.vPoints.elementAt(idx);
            ptTmp = gG.getMapPointFromPosition(gT, gdTmp, ptTmp);
            ptTmp.x -= p.x;
            ptTmp.y -= p.y;
            dT = ptTmp.x * ptTmp.x;
            dT += ptTmp.y * ptTmp.y;
            if (dT < dMin) {
                idxMin = idx;
                dMin = dT;
            }
        }
        return idxMin;
    }

    /**
     * Move a point to a new position.
     */
    public void movePointAt(int idx, Point p, GeoGraphable gG) {
        if ((idx < 0) || (idx >= myTrack.vPoints.size())) {
            return;
        }
        TrackPoint gT = (TrackPoint) myTrack.vPoints.elementAt(idx);
        gG.getPointFromMap(p.x, p.y, gT);
        updateDistancesFromIndex(idx);
    }

    /**
     * Invert the direction of the track.
     */
    public void reverse() {
        myTrack.reverse();
        updateDistancesFromIndex(0);
        myIndexSelected = 0;
        myIndexNext = 0;
    }

    /**
     * Visit next point on the track.
     */
    public void visitNext() {
        int n = myTrack.vPoints.size();
        if (n == 0) {
            return;
        }
        myIndexNext++;
        if (myIndexNext >= n) {
            myIndexNext--;
        }
    }

    /**
     * Set the next track point to visit.
     * All points before are visited too.
     */
    public void setNextTo(int idx) {
        if (idx < 0) {
            idx = -1;
        }
        if (idx >= myTrack.vPoints.size()) {
            idx = myTrack.vPoints.size();
        }
        myIndexNext = idx;
    }

    /**
     * Load the track from a file.
     */
    public void loadFromFile(File f) {
        myTrack = TrackRecord.readLogFile(f, myTrack, TrackRecord.OPTION_ENDTRACK);
        updateDistancesFromIndex(0);
        myIndexSelected = 0;
        myIndexNext = 0;
    }

    /**
     * Write the track to a file.
     */
    public boolean writeToFile(File f) {
        myTrack.reCompute();
        return myTrack.writeLogFile(f);
    }

    /**
     * Display track on a GeoGraphable.
     */
    public void drawIn(Graphics g, Rect r, GeoGraphable gG) {
        int nbP = myTrack.vPoints.size();
        int lX = -1;
        int lY = -1;
        int lXSym = -1;
        int lYSym = -1;
        g.setPen(thePenVisited);
        g.setDrawOp(g.DRAW_OVER);
        boolean isInVisitedMode = true;
        boolean wasPreviousVisible = true;
        //TODO draw an orientation on the track
        double dHdgCos = 0.0d;
        double dHdgSin = 0.0d;
        for (int idx = 0; idx < nbP; idx++) {
            GeoPoint gT = (GeoPoint) myTrack.vPoints.elementAt(idx);
            ptTmp = gG.getMapPointFromPosition(gT, gdTmp, ptTmp);
            int x = ptTmp.x;
            int y = ptTmp.y;
            boolean isCurrentVisible = r.isIn(x, y);
            if ((idx > 0) && (wasPreviousVisible || isCurrentVisible)) {
                int dA = x - lX;
                int d = dA * dA;
                dA = y - lY;
                d += dA * dA;
                if (d > 4) { // Mandatory otherwise the line is too short and
                    // nothing is drawn

                    // First the direction
                    dA = x - lXSym;
                    d = dA * dA;
                    dA = y - lYSym;
                    d += dA * dA;
                    if (d > DISTANCESYMBOLSSQUARE) {
                        if (idx < (nbP - 1)) {
                            dHdgCos = gT.headingTo((GeoPoint) myTrack.vPoints.elementAt(idx + 1));
                            dHdgCos = Math.PI * dHdgCos / 180.0d;
                            dHdgSin = Math.sin(dHdgCos);
                            dHdgCos = Math.cos(dHdgCos);
                        }
                        lXSym = x;
                        lYSym = y;
                        tPX[0] = (int) (6 * dHdgSin);
                        tPY[0] = (int) (-6 * dHdgCos);
                        tPX[1] = -(tPX[0] + tPY[0]) / 2;
                        tPY[1] = (tPX[0] - tPY[0]) / 2;
                        tPX[2] = -tPY[1];
                        tPY[2] = tPX[1];
                        g.translate(lXSym, lYSym);
                        g.fillPolygon(tPX, tPY, tPX.length);
                        g.translate(-lXSym, -lYSym);
//                        if(idx==(nbP-1)) {
//                            g.translate(x,y);
//                            g.fillPolygon(tPX,tPY,tPX.length);
//                            g.translate(-x,-y);
//                        }
                    }
                    // Then the line on top
                    g.drawLine(lX, lY, x, y);
                    lX = x;
                    lY = y;
                }
            } else {
                lX = x;
                lY = y;
                lXSym = x;
                lYSym = y;
            }
            wasPreviousVisible = isCurrentVisible;
            if (isCurrentVisible && (myIndexSelected == idx)) {
                drawHandle(g, x, y);
            }
            if ((isInVisitedMode) && (idx == myIndexNext)) {
                g.setPen(thePenNonVisited);
                isInVisitedMode = false;
            }
        } /* for(int idx=0; idx<nbP; idx++) */
        if (nbP == 1) {
            GeoPoint gT = (GeoPoint) myTrack.vPoints.elementAt(0);
            g.setPen(thePenNonVisited);
            ptTmp = gG.getMapPointFromPosition(gT, gdTmp, ptTmp);
            tPX[0] = 0;
            tPY[0] = -3;
            tPX[1] = -(tPX[0] + tPY[0]);
            tPY[1] = (tPX[0] - tPY[0]);
            tPX[2] = -tPY[1];
            tPY[2] = tPX[1];
            g.translate(ptTmp.x, ptTmp.y);
            g.fillPolygon(tPX, tPY, tPX.length);
            g.translate(-ptTmp.x, -ptTmp.y);
        }
    }

    /**
     * Draw the handle for a waypoint.
     */
    public void drawHandle(Graphics g, int x, int y) {
        g.setDrawOp(g.DRAW_XOR);
        Pen pP = g.getPen();
        g.setPen(PENHANDLE);
        g.fillRect(x - 2, y - 2, 5, 5);
        g.setDrawOp(g.DRAW_OVER);
        g.setPen(pP);
    }

    /**
     * Compute X and Y on map from point index in track.
     * @param idxP Point index to localise.
     * @param tmp GeoDirection reference to compute intermediate direction.
     * If <I><B>null</B></I> a new object is allocated.
     * @param pt point on bitmap image to compute.
     * If <I><B>null</B></I> a new Point object is allocated.
     * @return Computed point. Should be <I>pt</I> or a new Point object.
     */
    public Point getMapPointFromPosition(int idxP, GeoDirection tmp,
            Point pt, GeoGraphable gG) {
        if ((idxP < 0) || (idxP >= myTrack.vPoints.size())) {
            return pt;
        }

        GeoPoint pos = (GeoPoint) myTrack.vPoints.elementAt(idxP);

        return gG.getMapPointFromPosition(pos, tmp, pt);
    }
}
